package kha.graphics4;

import js.html.webgl.GL;
import haxe.io.Bytes;
import kha.js.graphics4.Graphics;

class CubeMap implements Canvas implements Resource {
	var myWidth: Int;
	var myHeight: Int;
	var format: TextureFormat;
	var renderTarget: Bool;
	var depthStencilFormat: DepthStencilFormat;
	var graphics4: kha.graphics4.Graphics;

	public var frameBuffer: Dynamic = null;
	public var texture: Dynamic = null;
	public var depthTexture: Dynamic = null;
	public var isDepthAttachment: Bool = false;

	// WebGL2 constants
	static inline var GL_RGBA16F = 0x881A;
	static inline var GL_RGBA32F = 0x8814;
	static inline var GL_R16F = 0x822D;
	static inline var GL_R32F = 0x822E;
	static inline var GL_DEPTH_COMPONENT24 = 0x81A6;
	static inline var GL_DEPTH24_STENCIL8 = 0x88F0;
	static inline var GL_DEPTH32F_STENCIL8 = 0x8CAD;

	function new(size: Int, format: TextureFormat, renderTarget: Bool, depthStencilFormat: DepthStencilFormat) {
		myWidth = size;
		myHeight = size;
		this.format = format;
		this.renderTarget = renderTarget;
		this.depthStencilFormat = depthStencilFormat;
		if (renderTarget)
			createTexture();
	}

	public static function createRenderTarget(size: Int, format: TextureFormat = null, depthStencil: DepthStencilFormat = null): CubeMap {
		if (format == null)
			format = TextureFormat.RGBA32;
		if (depthStencil == null)
			depthStencil = NoDepthAndStencil;
		return new CubeMap(size, format, true, depthStencil);
	}

	function createTexture() {
		if (SystemImpl.gl == null)
			return;

		texture = SystemImpl.gl.createTexture();
		SystemImpl.gl.bindTexture(GL.TEXTURE_CUBE_MAP, texture);

		SystemImpl.gl.texParameteri(GL.TEXTURE_CUBE_MAP, GL.TEXTURE_MAG_FILTER, GL.LINEAR);
		SystemImpl.gl.texParameteri(GL.TEXTURE_CUBE_MAP, GL.TEXTURE_MIN_FILTER, GL.LINEAR);
		SystemImpl.gl.texParameteri(GL.TEXTURE_CUBE_MAP, GL.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE);
		SystemImpl.gl.texParameteri(GL.TEXTURE_CUBE_MAP, GL.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE);

		if (renderTarget) {
			frameBuffer = SystemImpl.gl.createFramebuffer();
			SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, frameBuffer);

			switch (format) {
				case DEPTH16:
					for (i in 0...6)
						SystemImpl.gl.texImage2D(GL.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, SystemImpl.gl2 ? GL.DEPTH_COMPONENT16 : GL.DEPTH_COMPONENT, myWidth,
							myHeight, 0, GL.DEPTH_COMPONENT, GL.UNSIGNED_SHORT, null);
				case RGBA128:
					for (i in 0...6)
						SystemImpl.gl.texImage2D(GL.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, SystemImpl.gl2 ? GL_RGBA32F : GL.RGBA, myWidth, myHeight, 0, GL.RGBA,
							GL.FLOAT, null);
				case RGBA64:
					for (i in 0...6)
						SystemImpl.gl.texImage2D(GL.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, SystemImpl.gl2 ? GL_RGBA16F : GL.RGBA, myWidth, myHeight, 0, GL.RGBA,
							SystemImpl.halfFloat.HALF_FLOAT_OES, null);
				case RGBA32:
					for (i in 0...6)
						SystemImpl.gl.texImage2D(GL.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL.RGBA, myWidth, myHeight, 0, GL.RGBA, GL.UNSIGNED_BYTE, null);
				case A32:
					for (i in 0...6)
						SystemImpl.gl.texImage2D(GL.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, SystemImpl.gl2 ? GL_R32F : GL.ALPHA, myWidth, myHeight, 0, GL.ALPHA,
							GL.FLOAT, null);
				case A16:
					for (i in 0...6)
						SystemImpl.gl.texImage2D(GL.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, SystemImpl.gl2 ? GL_R16F : GL.ALPHA, myWidth, myHeight, 0, GL.ALPHA,
							SystemImpl.halfFloat.HALF_FLOAT_OES, null);
				default:
					for (i in 0...6)
						SystemImpl.gl.texImage2D(GL.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL.RGBA, myWidth, myHeight, 0, GL.RGBA, GL.UNSIGNED_BYTE, null);
			}

			if (format == DEPTH16) {
				SystemImpl.gl.texParameteri(GL.TEXTURE_CUBE_MAP, GL.TEXTURE_MAG_FILTER, GL.NEAREST);
				SystemImpl.gl.texParameteri(GL.TEXTURE_CUBE_MAP, GL.TEXTURE_MIN_FILTER, GL.NEAREST);
				isDepthAttachment = true;
				// Some WebGL implementations throw incomplete framebuffer error, create color attachment
				if (!SystemImpl.gl2) {
					var colortex = SystemImpl.gl.createTexture();
					SystemImpl.gl.bindTexture(GL.TEXTURE_CUBE_MAP, colortex);
					for (i in 0...6) {
						SystemImpl.gl.texImage2D(GL.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL.RGBA, myWidth, myHeight, 0, GL.RGBA, GL.UNSIGNED_BYTE, null);
						SystemImpl.gl.framebufferTexture2D(GL.FRAMEBUFFER, GL.COLOR_ATTACHMENT0, GL.TEXTURE_CUBE_MAP_POSITIVE_X + i, colortex, 0);
					}
					SystemImpl.gl.bindTexture(GL.TEXTURE_CUBE_MAP, texture);
				}
			}

			initDepthStencilBuffer(depthStencilFormat);
			SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, null);
		}

		SystemImpl.gl.bindTexture(GL.TEXTURE_CUBE_MAP, null);
	}

	function initDepthStencilBuffer(depthStencilFormat: DepthStencilFormat) {
		switch (depthStencilFormat) {
			case NoDepthAndStencil:
			case DepthOnly, Depth16:
				{
					depthTexture = SystemImpl.gl.createTexture();
					SystemImpl.gl.bindTexture(GL.TEXTURE_CUBE_MAP, depthTexture);
					if (depthStencilFormat == DepthOnly)
						SystemImpl.gl.texImage2D(GL.TEXTURE_CUBE_MAP, 0, SystemImpl.gl2 ? GL_DEPTH_COMPONENT24 : GL.DEPTH_COMPONENT, myWidth, myHeight, 0,
							GL.DEPTH_COMPONENT, GL.UNSIGNED_INT, null);
					else
						SystemImpl.gl.texImage2D(GL.TEXTURE_CUBE_MAP, 0, SystemImpl.gl2 ? GL.DEPTH_COMPONENT16 : GL.DEPTH_COMPONENT, myWidth, myHeight, 0,
							GL.DEPTH_COMPONENT, GL.UNSIGNED_SHORT, null);
					SystemImpl.gl.texParameteri(GL.TEXTURE_CUBE_MAP, GL.TEXTURE_MAG_FILTER, GL.NEAREST);
					SystemImpl.gl.texParameteri(GL.TEXTURE_CUBE_MAP, GL.TEXTURE_MIN_FILTER, GL.NEAREST);
					SystemImpl.gl.texParameteri(GL.TEXTURE_CUBE_MAP, GL.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE);
					SystemImpl.gl.texParameteri(GL.TEXTURE_CUBE_MAP, GL.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE);
					SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, frameBuffer);
					SystemImpl.gl.framebufferTexture2D(GL.FRAMEBUFFER, GL.DEPTH_ATTACHMENT, GL.TEXTURE_CUBE_MAP, depthTexture, 0);
				}
			case DepthAutoStencilAuto, Depth24Stencil8, Depth32Stencil8:
				depthTexture = SystemImpl.gl.createTexture();
				SystemImpl.gl.bindTexture(GL.TEXTURE_CUBE_MAP, depthTexture);
				SystemImpl.gl.texImage2D(GL.TEXTURE_CUBE_MAP, 0, SystemImpl.gl2 ? GL_DEPTH24_STENCIL8 : GL.DEPTH_STENCIL, myWidth, myHeight, 0,
					GL.DEPTH_STENCIL, SystemImpl.depthTexture.UNSIGNED_INT_24_8_WEBGL, null);
				SystemImpl.gl.texParameteri(GL.TEXTURE_CUBE_MAP, GL.TEXTURE_MAG_FILTER, GL.NEAREST);
				SystemImpl.gl.texParameteri(GL.TEXTURE_CUBE_MAP, GL.TEXTURE_MIN_FILTER, GL.NEAREST);
				SystemImpl.gl.texParameteri(GL.TEXTURE_CUBE_MAP, GL.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE);
				SystemImpl.gl.texParameteri(GL.TEXTURE_CUBE_MAP, GL.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE);
				SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, frameBuffer);
				SystemImpl.gl.framebufferTexture2D(GL.FRAMEBUFFER, GL.DEPTH_STENCIL_ATTACHMENT, GL.TEXTURE_CUBE_MAP, depthTexture, 0);
		}
	}

	public function set(stage: Int): Void {
		SystemImpl.gl.activeTexture(GL.TEXTURE0 + stage);
		SystemImpl.gl.bindTexture(GL.TEXTURE_CUBE_MAP, texture);
	}

	public function setDepth(stage: Int): Void {
		SystemImpl.gl.activeTexture(GL.TEXTURE0 + stage);
		SystemImpl.gl.bindTexture(GL.TEXTURE_CUBE_MAP, depthTexture);
	}

	public function unload(): Void {}

	public function lock(level: Int = 0): Bytes {
		return null;
	}

	public function unlock(): Void {}

	public var width(get, never): Int;

	function get_width(): Int {
		return myWidth;
	}

	public var height(get, never): Int;

	function get_height(): Int {
		return myHeight;
	}

	public var g1(get, never): kha.graphics1.Graphics;

	function get_g1(): kha.graphics1.Graphics {
		return null;
	}

	public var g2(get, never): kha.graphics2.Graphics;

	function get_g2(): kha.graphics2.Graphics {
		return null;
	}

	public var g4(get, never): kha.graphics4.Graphics;

	function get_g4(): kha.graphics4.Graphics {
		if (graphics4 == null) {
			graphics4 = new Graphics(this);
		}
		return graphics4;
	}
}
